/*

   mod_limitiprequest 0.1
   Copyright (C) 2009 Costantino Leandro <lcostantino@gmail.com>

 * This is a test module, do not use on production.
 *
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

   This module was based on SpeedLimit idea and:

      mod_limitipconn 0.23 Copyright (C) 2000-2008 David Jao and Niklas Edmundsson
      Original Comments Kept.
 */

#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_protocol.h"
#include "http_core.h"
#include "http_main.h"
#include "http_log.h"
#include "ap_mpm.h"
#include "apr_strings.h"

#include <mysql/mysql.h>
#define MODULE_NAME "mod_limitiprequest"
#define MODULE_VERSION "0.1"

module AP_MODULE_DECLARE_DATA limitiprequest_module;

static int server_limit, thread_limit;

typedef enum
{
   IPLIMIT_ID=0,IPLIMIT_IP,IPLIMIT_AGENT,IPLIMIT_FIRST,IPLIMIT_LAST,IPLIMIT_HITS,IPLIMIT_LOCKED
}
ROW_ORDER;

typedef struct
{
   int id;
   char ip[32];
   char agent[64];
   time_t first;
   time_t last;
   unsigned int hits;
   int locked;
   int blessed;
}
user_limit;

typedef struct
{
   signed int limit;       /* max number of connections per IP */
   unsigned int forgive_time_minutes; /* Elapsed time to forgive IP. Minutes */

   apr_array_header_t *no_limit;

   apr_array_header_t *excl_limit;
   signed int db_force_check;
   char    *mysql_host;
   char    *mysql_port;
   char    *mysql_user;
   char    *mysql_pass;
   char    *mysql_db;
   char  *mysql_table;

}
limitiprequest_config;

static int updateUserData(MYSQL *db,const char *address,request_rec *r, user_limit *user,char *table)
{
   MYSQL_RES *result;
   MYSQL_ROW row;
   int rc;
   char query[100];

   snprintf(query,100,"select * from %s where ip='%s'",table,address);
   rc=mysql_query(db,query);
   if(rc != 0)
     {
	ap_log_rerror(APLOG_MARK,APLOG_INFO,0,r, "Query Error on hitsUpdates %s\n",mysql_error(db));
	return 0;
     }
   result = mysql_use_result(db);
   while( ( row = mysql_fetch_row(result))!= NULL)
     {
	user->hits = atoi(row[IPLIMIT_HITS]); //verificar que es numeric...
	user->locked = atoi(row[IPLIMIT_LOCKED]);
	break;;
     }
   mysql_free_result(result);
   return 1;
}

static int setLockedUser(MYSQL *db, const char *address, char *table, unsigned int limit )
{
   char query[100];
   snprintf(query,100,"UPDATE %s set locked=1,hits=hits+1 where ip='%s' and locked=0 and hits>=%d",table,address,limit);

   if( mysql_query(db,query) == 0 )
     {
	return mysql_affected_rows(db);
     }

   return 0;
}

static int saveUserLimit( MYSQL *db, user_limit *user,request_rec *r,char *table)
{
   MYSQL_RES *result;
   MYSQL_ROW *row;
   char query[100];
   /* Update User data */
   if( user->id > 0 )
     {
	//here we could bind parameters for datetime, but  i am handling it like a "number"
	snprintf(query,100, "update %s set first='%ld',last='%ld',%s,locked=%d where id='%d'",table,user->first,user->last,
		 (user->blessed ) ? "hits=1" : "hits=hits+1",user->locked,user->id);
     }
   /* New user */
   else
     {
        snprintf(query,100, "insert into %s values ('','%s','%s','%ld','%ld','%d','%d')",
		 table,user->ip,"notused",user->first,user->last,1,user->locked);
     }

   if(mysql_query(db, query) != 0 )
     {
	ap_log_rerror(APLOG_MARK,APLOG_INFO,0,r, "saveUser Query Failed %s\n",mysql_error(db));

	return -1;
     }
   return 0;

}

static int findUserLimit( MYSQL *db, const char *address, user_limit *user,request_rec *r,char *table )
{
   MYSQL_RES *result;
   MYSQL_ROW row;
   char query[100];

   snprintf(query,100,"select * from %s where ip='%s'",table,address);
   if(mysql_query(db, query) != 0)
     {
	ap_log_rerror(APLOG_MARK,APLOG_INFO,0,r, "FindUserLimit Query Error %s\n",mysql_error(db));
	return -1;
     }
   result = mysql_use_result(db);
   if( mysql_num_fields(result) != (IPLIMIT_LOCKED+1) )
     {
	mysql_free_result(result);
	ap_log_rerror(APLOG_MARK,APLOG_INFO,0,r, "FindUserLimit fields number mismatch for the row");
	return -1;
     }

   while( (row = mysql_fetch_row(result)) != NULL )
     {

	user->id = atoi(row[IPLIMIT_ID]);
	apr_cpystrn(user->ip, row[IPLIMIT_IP],32);
	apr_cpystrn(user->agent, row[IPLIMIT_AGENT],64);
	user->first = atol(row[IPLIMIT_FIRST]);
	user->last = atol(row[IPLIMIT_LAST]);
	user->hits = atoi(row[IPLIMIT_HITS]);
	user->locked = atoi(row[IPLIMIT_LOCKED]);
	break;;
     }
   mysql_free_result(result);
   if(! user->id)
     {
	return 0; ;
     }

   return user->id;
}

static int process_db( const char *address,request_rec *r,limitiprequest_config *cfg )
{
   MYSQL *db;
   int rc;
   user_limit user;
   time_t now,last;
   int force=1;
   if( cfg->mysql_user == NULL || cfg->mysql_db == NULL || cfg->mysql_table == NULL)
     {
	ap_log_rerror(APLOG_MARK,APLOG_INFO,0,r, "IPLIMIT DB Data missing %s:%s:%s:%s",cfg->mysql_host,cfg->mysql_user,cfg->mysql_db,cfg->mysql_table);
	return DECLINED;
     }
   db = mysql_init(NULL);
   if( !mysql_real_connect(db,  cfg->mysql_host,cfg->mysql_user,cfg->mysql_pass,cfg->mysql_db,cfg->mysql_port!=NULL?atoi(cfg->mysql_port):3306,NULL,0))
     {
	ap_log_rerror(APLOG_MARK,APLOG_INFO,0,r, "Cannot open Database %s",mysql_error(db));
	//keep working in this case
	return DECLINED;
     }
   user.id = 0;
   user.blessed = 0;
   //minutes.. based on SpeedLimit
   now = time(NULL)/60;

   if( findUserLimit(db, address,&user,r,cfg->mysql_table) < 0)
     {
	mysql_close(db);
	return DECLINED;
     }

   if( user.id == 0 )
     {
	ap_log_rerror(APLOG_MARK,APLOG_INFO,0,r, "New IP %s!\n",address);
	memset(user.agent,0x00,sizeof(user.agent));
	user.last = user.first = now;
	user.hits = user.locked = 0;
	strncpy(user.ip,address,32);
	//save early to avoid lossing fields by duplicate indexs..

	if( saveUserLimit(db,&user,r,cfg->mysql_table) < 0)
	  {
	     /* If force check, try to reupdate user, maybe some thread already inserted it.
	      * Unique key wont allow duplicates. If no force check is set, we only miss some hits */
	     if( cfg->db_force_check == 1 )
	       {
		  if( findUserLimit(db, address,&user,r,cfg->mysql_table) < 0 )
		    {
		       mysql_close(db);
		       return DECLINED;
		    }

		  ap_log_rerror(APLOG_MARK,APLOG_INFO,0,r, "User inser failed (another thread?), but new user found\n");
	       }
	     else
	       {
		  mysql_close(db);
		  return DECLINED;
	       }
	  }
     }

   if( user.locked && ((now  - user.last) > cfg->forgive_time_minutes ))
     {
	ap_log_rerror(APLOG_MARK,APLOG_INFO,0,r, "IP %s forgived",address);
	user.last=now;
	user.first=now;
	user.locked = 0;
	user.hits= 1;
	user.blessed = 1;
	saveUserLimit(db,&user,r,cfg->mysql_table);
	mysql_close(db);
	return DECLINED;
     }
   if(cfg->db_force_check == 1 )
     {
	updateUserData(db,address,r,&user,cfg->mysql_table);
     }

   user.hits+=1;
   user.last = now;
   if( user.locked == 1 )
     {
	ap_log_rerror(APLOG_MARK,APLOG_INFO,0,r, "IP BLocked %s",address);
	mysql_close(db);
        return  HTTP_SERVICE_UNAVAILABLE;
     }

   if( user.hits >= cfg->limit )
     {
	setLockedUser(db,address,cfg->mysql_table, cfg->limit-1);
	mysql_close(db);
	return  HTTP_SERVICE_UNAVAILABLE;
     }

   saveUserLimit(db,&user,r,cfg->mysql_table);

   ap_log_rerror(APLOG_MARK,APLOG_DEBUG,0,r, "User Data [%s] Hits [%d]\n",user.ip,user.hits);
   mysql_close(db);
   return DECLINED;
}

static limitiprequest_config *create_config(apr_pool_t *p)
{
   limitiprequest_config *cfg = (limitiprequest_config *)
     apr_pcalloc(p, sizeof (*cfg));

    /* default configuration: no limit, and both arrays are empty */
   cfg->limit = 0;
   cfg->forgive_time_minutes = 0;

   cfg->mysql_host="localhost";
   cfg->mysql_port="3306";
   cfg->mysql_user=NULL;
   cfg->mysql_pass=NULL;
   cfg->mysql_db=NULL;

   cfg->no_limit = apr_array_make(p, 0, sizeof(char *));
   cfg->excl_limit = apr_array_make(p, 0, sizeof(char *));
   cfg->db_force_check = 0;
   return cfg;
}

/* Create per-server configuration structure. Used by the quick handler. */
static void *limitiprequest_create_config(apr_pool_t *p, server_rec *s)
{
   return create_config(p);
}

/* Create per-directory configuration structure. Used by the normal handler. */
static void *limitiprequest_create_dir_config(apr_pool_t *p, char *path)
{
   return create_config(p);
}

/* Generic function to check a request against a config. */
static int check_limit(request_rec *r, limitiprequest_config *cfg)
{
    /* convert Apache arrays to normal C arrays */
   char **nolim = (char **) cfg->no_limit->elts;
   char **exlim = (char **) cfg->excl_limit->elts;

   const char *address;
   const char *agent;
    /* loop index variables */
   int i;
   int j;
   int rc;

    /* Content-type of the current request */
   const char *content_type;

    /* We decline to handle subrequests: otherwise, in the next step we
     * could get into an infinite loop. */
   if (!ap_is_initial_req(r))
     {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
		      "mod_limitiprequest: SKIPPED: Not initial request");
        return DECLINED;
     }

   /* A limit value of 0 or less, by convention, means no limit. */
   if (cfg->limit <= 0)
     {
        return DECLINED;
     }

   address = r->connection->remote_ip;

    /* Only check the MIME-type if we have MIME-type stuff in our config.
       The extra subreq can be quite expensive. */
   if(cfg->no_limit->nelts > 0 || cfg->excl_limit->nelts > 0)
     {
        /* Look up the Content-type of this request. We need a subrequest
         * here since this module might be called before the URI has been
         * translated into a MIME type. */
        content_type = ap_sub_req_lookup_uri(r->uri, r, NULL)->content_type;

        /* If there's no Content-type, use the default. */
        if (!content_type)
	  {
	     content_type = ap_default_type(r);
	  }

        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
		      "mod_limitiprequest: uri: %s  Content-Type: %s",
		      r->uri, content_type);

        /* Cycle through the exempt list; if our content_type is exempt,
         * return OK */
        for (i = 0; i < cfg->no_limit->nelts; i++)
	  {
	     if ((ap_strcasecmp_match(content_type, nolim[i]) == 0)
		 || (strncmp(nolim[i], content_type, strlen(nolim[i])) == 0))
	       {
		  ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
				"mod_limitiprequest: OK: %s exempt", content_type);
		  return DECLINED;
	       }
	  }

        /* Cycle through the exclusive list, if it exists; if our MIME type
         * is not present, bail out */
        if (cfg->excl_limit->nelts)
	  {
	     int excused = 1;
	     for (i = 0; i < cfg->excl_limit->nelts; i++)
	       {
		  if ((ap_strcasecmp_match(content_type, exlim[i]) == 0)
		      ||
		      (strncmp(exlim[i], content_type, strlen(exlim[i])) == 0))
		    {
		       excused = 0;
		    }
	       }
	     if (excused)
	       {
		  ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
				"mod_limitiprequest: OK: %s not excluded",
				content_type);
		  return DECLINED;
	       }
	  }
     }

   rc=process_db(address,r,cfg);

   return rc;

}

/* Normal handler. This function is invoked to handle limitiprequest
   directives within a per-directory context. */
static int limitiprequest_handler(request_rec *r)
{
    /* get configuration information */
   limitiprequest_config *cfg = (limitiprequest_config *)
     ap_get_module_config(r->per_dir_config, &limitiprequest_module);

   int result;

   ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
		 "mod_limitiprequest: Entering normal handler");
   result = check_limit(r, cfg);
   ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
		 "mod_limitiprequest: Exiting normal handler");
   return result;
}

/*
   Comments from original module, this was based on. (nombre)
   Quick handler. This function is invoked to handle limitiprequest
   directives within a per-server context.

   The handler runs as a quick handler so we can arrange for it to be
   called before mod_cache. Being a quick handler means that we have a
   lot of limitations, the basic ones are that the only thing we know
   is the URL and that if we return OK it means that we handle the
   entire reply of the request including populating the brigades with
   data.

   Because this is a quick handler, it _CANNOT_ process per-directory
   configuration directives. Therefore, if you have any per-directory
   configuration directives, they will _NOT_ be handled here, and
   mod_cache will get to them first.
*/
static int limitiprequest_quick_handler(request_rec *r, int lookup)
{
    /* get configuration information */
   limitiprequest_config *cfg = (limitiprequest_config *)
     ap_get_module_config(r->server->module_config, &limitiprequest_module);

   int result;

   ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
		 "mod_limitiprequest: Entering quick handler");
   result = check_limit(r, cfg);
   ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
		 "mod_limitiprequest: Exiting quick handler");
   return result;
}

/* Parse the MaxRequestPerIP directive */
static const char *iplimit_config_cmd(cmd_parms *parms, void *mconfig,
				      const char *arg)
{
   limitiprequest_config *cfg = (limitiprequest_config *) mconfig;
   limitiprequest_config *scfg = (limitiprequest_config *)
     ap_get_module_config(parms->server->module_config, &limitiprequest_module);

   signed long int limit = strtol(arg, (char **) NULL, 10);

   if ((limit > 65535) || (limit < 0))
     {
        return "Integer overflow or invalid number";
     }

   if (parms->path != NULL)
     {
      /* Per-directory context */
	cfg->limit = limit;

     }
   else
     {
      /* Per-server context */

	scfg->limit = limit;
     }
   return NULL;
}
/* Parse the ForceCheck directive */
static const char *iplimit_cfg_force_check(cmd_parms *parms, void *mconfig,
					   const char *arg)
{
   limitiprequest_config *cfg = (limitiprequest_config *) mconfig;
   limitiprequest_config *scfg = (limitiprequest_config *)
     ap_get_module_config(parms->server->module_config, &limitiprequest_module);

   signed int force = atoi(arg);
   if( force != 1)
     {
	force = 0;
     }

   if (parms->path != NULL)
     {
      /* Per-directory context */
	cfg->db_force_check = force;

     }
   else
     {
      /* Per-server context */

	scfg->db_force_check  = force;
     }
   return NULL;
}

/* Parse ForgiveMinTime directive */
static const char *forgive_time_minutes_config_cmd(cmd_parms *parms, void *mconfig,
						   const char *arg)
{
   limitiprequest_config *cfg = (limitiprequest_config *) mconfig;
   limitiprequest_config *scfg = (limitiprequest_config *)
     ap_get_module_config(parms->server->module_config, &limitiprequest_module);

   signed long int minutes = strtol(arg, (char **) NULL, 10);

       /* max 45 days? */
   if ((minutes> 65535) || (minutes < 0))
     {
        return "Integer overflow or invalid number";
     }

   if (parms->path != NULL)
     {
      /* Per-directory context */
	cfg->forgive_time_minutes = minutes;

     }
   else
     {
      /* Per-server context */

	scfg->forgive_time_minutes = minutes;
     }
   return NULL;
}
/* Database Host and Port */
static const char *iplimit_cfg_set_dbserver(cmd_parms *parms, void *mconfig, char *srv, char *port)
{
   limitiprequest_config *cfg = (limitiprequest_config *) mconfig;
   limitiprequest_config *scfg = (limitiprequest_config *)
     ap_get_module_config(parms->server->module_config, &limitiprequest_module);
/*its really needed here? db per host / vhost? */
   if (parms->path != NULL)
     {
	cfg->mysql_host = apr_pstrdup(parms->pool,srv);
	cfg->mysql_port = apr_pstrdup(parms->pool,port);
     }
   else
     {
	scfg->mysql_host = apr_pstrdup(parms->pool,srv);
	scfg->mysql_port = apr_pstrdup(parms->pool,port);
     }
   return NULL;

}
/* Database User */
static const char *iplimit_cfg_set_dbuser(cmd_parms *parms, void *mconfig,
					  const char *arg)
{
   limitiprequest_config *cfg = (limitiprequest_config *) mconfig;
   limitiprequest_config *scfg = (limitiprequest_config *)
     ap_get_module_config(parms->server->module_config, &limitiprequest_module);

   if (parms->path != NULL)
     {
	cfg->mysql_user = apr_pstrdup(parms->pool,arg);

     }
   else
     {
	scfg->mysql_user = apr_pstrdup(parms->pool,arg);

     }
   return NULL;
}
/* Database Password */
static const char *iplimit_cfg_set_dbpass(cmd_parms *parms, void *mconfig,
					  const char *arg)
{
   limitiprequest_config *cfg = (limitiprequest_config *) mconfig;
   limitiprequest_config *scfg = (limitiprequest_config *)
     ap_get_module_config(parms->server->module_config, &limitiprequest_module);

   if (parms->path != NULL)
     {
	cfg->mysql_pass = apr_pstrdup(parms->pool,arg);

     }
   else
     {
	scfg->mysql_pass = apr_pstrdup(parms->pool,arg);

     }
   return NULL;
}
/* Database Name */
static const char *iplimit_cfg_set_dbname(cmd_parms *parms, void *mconfig,
					  const char *arg)
{
   limitiprequest_config *cfg = (limitiprequest_config *) mconfig;
   limitiprequest_config *scfg = (limitiprequest_config *)
     ap_get_module_config(parms->server->module_config, &limitiprequest_module);

   if (parms->path != NULL)
     {
	cfg->mysql_db = apr_pstrdup(parms->pool,arg);

     }
   else
     {
	scfg->mysql_db = apr_pstrdup(parms->pool,arg);

     }
   return NULL;
}
static const char *iplimit_cfg_set_dbtable(cmd_parms *parms, void *mconfig,
					   const char *arg)
{
   limitiprequest_config *cfg = (limitiprequest_config *) mconfig;
   limitiprequest_config *scfg = (limitiprequest_config *)
     ap_get_module_config(parms->server->module_config, &limitiprequest_module);

   if (parms->path != NULL)
     {
	cfg->mysql_table = apr_pstrdup(parms->pool,arg);

     }
   else
     {
	scfg->mysql_table = apr_pstrdup(parms->pool,arg);

     }
   return NULL;
}

/* Parse the NoIPLimit directive */
static const char *no_iplimit_config_cmd(cmd_parms *parms, void *mconfig,
					 const char *arg)
{
   limitiprequest_config *cfg = (limitiprequest_config *) mconfig;
   limitiprequest_config *scfg = (limitiprequest_config *)
     ap_get_module_config(parms->server->module_config, &limitiprequest_module);

   if (parms->path != NULL)
     {
      /* Per-directory context */
	*(char **) apr_array_push(cfg->no_limit) =
	  apr_pstrdup(parms->pool, arg);
     }
   else
     {
      /* Per-server context */
	*(char **) apr_array_push(scfg->no_limit) =
	  apr_pstrdup(parms->pool, arg);
     }
   return NULL;
}

/* Parse the OnlyIPLimit directive */
static const char *excl_iplimit_config_cmd(cmd_parms *parms, void *mconfig,
					   const char *arg)
{
   limitiprequest_config *cfg = (limitiprequest_config *) mconfig;
   limitiprequest_config *scfg = (limitiprequest_config *)
     ap_get_module_config(parms->server->module_config, &limitiprequest_module);

   if (parms->path != NULL)
     {
      /* Per-directory context */
	*(char **) apr_array_push(cfg->excl_limit) =
	  apr_pstrdup(parms->pool, arg);
     }
   else
     {
      /* Per-server context */
	*(char **) apr_array_push(scfg->excl_limit) =
	  apr_pstrdup(parms->pool, arg);
     }
   return NULL;
}

/* Array describing structure of configuration directives */
static command_rec limitiprequest_cmds[] =
{
   AP_INIT_TAKE1("MaxRequestPerIP", iplimit_config_cmd, NULL, OR_LIMIT|RSRC_CONF,
		 "maximum request per IP address"),
   AP_INIT_TAKE1("ForgiveMinTime", forgive_time_minutes_config_cmd, NULL, OR_LIMIT|RSRC_CONF,
		 "Minutes after forgive IP to make request again"),
   AP_INIT_ITERATE("NoIPLimit", no_iplimit_config_cmd, NULL, OR_LIMIT|RSRC_CONF,
		   "MIME types for which limit echecking is disabled"),
   AP_INIT_ITERATE("OnlyIPLimit", excl_iplimit_config_cmd, NULL,
		   OR_LIMIT|RSRC_CONF, "restrict limit checking to these MIME types only"),
   AP_INIT_TAKE1("ForceCheck", iplimit_cfg_force_check, NULL, OR_LIMIT|RSRC_CONF,"Force database values check after data load."),
   AP_INIT_TAKE1("DbUser",(void *)iplimit_cfg_set_dbuser, NULL, OR_LIMIT|RSRC_CONF, "Set User used for connection"),
   AP_INIT_TAKE1("DbPass",(void *)iplimit_cfg_set_dbpass, NULL, OR_LIMIT|RSRC_CONF, "Set Pass used for connection"),
   AP_INIT_TAKE1("DbName",(void *)iplimit_cfg_set_dbname, NULL, OR_LIMIT|RSRC_CONF, "Set Database used for connection"),
   AP_INIT_TAKE1("DbTable",(void *)iplimit_cfg_set_dbtable, NULL, OR_LIMIT|RSRC_CONF, "Set Table used for connection"),
   AP_INIT_TAKE2("DbServer",(void *)iplimit_cfg_set_dbserver, NULL, OR_LIMIT|RSRC_CONF, "Set Server used for connection"),
     {NULL},

};

/* Set up startup-time initialization */
static int limitiprequest_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
{
   ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
		MODULE_NAME " " MODULE_VERSION " started.");
   ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
   ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit);
   return OK;
}

static void register_hooks(apr_pool_t *p)
{
   static const char * const after_me[] = { "mod_cache.c", NULL };

   ap_hook_post_config(limitiprequest_init, NULL, NULL, APR_HOOK_MIDDLE);
    /* From Original Module:
       We check against the per-server configuration directives in a
       quick handler so that we can deny connections before mod_cache
       gets to them.  This method does not work for per-directory
       configuration directives.  If you are using mod_cache, please
       avoid per-directory configuration directives for limitiprequest.
    */
   ap_hook_quick_handler(limitiprequest_quick_handler,NULL,after_me,
			 APR_HOOK_FIRST);
   ap_hook_access_checker(limitiprequest_handler,NULL,NULL,APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA limitiprequest_module =
{
   STANDARD20_MODULE_STUFF,
     limitiprequest_create_dir_config, /* create per-dir config structures */
     NULL,                       /* merge  per-dir    config structures */
     limitiprequest_create_config,  /* create per-server config structures */
     NULL,                       /* merge  per-server config structures */
     limitiprequest_cmds,           /* table of config file commands       */
     register_hooks
};
